Preprocesamiento de datos#

La limpieza de datos es un paso crítico en el desarrollo de modelos de machine learning, ya que garantiza que los datos utilizados para entrenar y evaluar los modelos sean precisos, confiables y representativos, lo que a su vez mejora la calidad y la eficacia de los modelos resultantes.

import pandas as pd
from numpy import NaN
import numpy as np
import requests
import plotly.express as px
import os
df = pd.read_csv("Data/raw_data.csv",low_memory=False)
df.head()
Unnamed: 0 departamento municipio codigo_dane armas_medios fecha_hecho genero grupo_etario cantidad
0 299006 ANTIOQUIA AMAGÁ 5030000 ARMA BLANCA / CORTOPUNZANTE 2019-01-01 FEMENINO ADULTOS 1
1 299007 ANTIOQUIA EL SANTUARIO 5697000 ARMA BLANCA / CORTOPUNZANTE 2019-01-01 FEMENINO ADULTOS 1
2 299008 ANTIOQUIA MEDELLÍN (CT) 5001000 ARMA BLANCA / CORTOPUNZANTE 2019-01-01 MASCULINO ADULTOS 2
3 299009 ANTIOQUIA NARIÑO 5483000 ARMA BLANCA / CORTOPUNZANTE 2019-01-01 FEMENINO ADULTOS 1
4 299010 ATLÁNTICO BARRANQUILLA (CT) 8001000 ARMA BLANCA / CORTOPUNZANTE 2019-01-01 FEMENINO ADULTOS 3

Ajustes para columnas, valores nulos y duplicados

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 276715 entries, 0 to 276714
Data columns (total 9 columns):
 #   Column        Non-Null Count   Dtype 
---  ------        --------------   ----- 
 0   Unnamed: 0    276715 non-null  int64 
 1   departamento  276715 non-null  object
 2   municipio     276715 non-null  object
 3   codigo_dane   276715 non-null  object
 4   armas_medios  276715 non-null  object
 5   fecha_hecho   276715 non-null  object
 6   genero        276715 non-null  object
 7   grupo_etario  275104 non-null  object
 8   cantidad      276715 non-null  int64 
dtypes: int64(2), object(7)
memory usage: 19.0+ MB

Eliminamos la columna Codigo_dane, ya que representa el identificador del caso registrado. No es útil para el análisis.

df = df.drop(['codigo_dane','Unnamed: 0'],axis=1)

Tratamiento de Datos Faltantes y Atípicos#

df.isnull().sum()
departamento       0
municipio          0
armas_medios       0
fecha_hecho        0
genero             0
grupo_etario    1611
cantidad           0
dtype: int64
fig = px.box(df, x = 'grupo_etario', y = 'cantidad', title="Cantidad de Casos por Grupo Etario", notched = False)
fig.show()

Este gráfico muestra la distribución de los casos por grupos etarios (adultos, adolescentes, menores y no reportados). Lo más notable es el pico extremadamente alto en la categoría “ADULTOS”, que indica que este grupo representa una proporción muy significativa de los casos totales en comparación con los demás grupos. En contraste, los grupos de “ADOLESCENTES”, “MENORES” y “NO REPORTADO” tienen valores mucho más bajos, sugiriendo que tienen una presencia mucho menor en el total de casos.

fig = px.box(df, x = 'genero', y = 'cantidad', title="Cantidad de Casos por Género", notched = True)
fig.show()
fig = px.box(df, x = 'armas_medios', y = 'cantidad',title= "Cantidad de Casos por Tipo de Arma" ,notched = True)
fig.show()
fig = px.box(df, x = 'departamento', y = 'cantidad',title= "Cantidad de Casos por Departamento" ,notched = True)
fig.show()
# Se reemplaza 'NO REPORTA' con valores NaN
df.replace(['NO REPORTA', 'NO REPORTADO', '-'], np.nan, inplace=True)
empty_info = df.isnull().sum()*100/df.shape[0]
print(f'{empty_info}\n\n{df.isnull().sum()}')
departamento     0.001084
municipio        0.001084
armas_medios    18.749616
fecha_hecho      0.000000
genero           0.104440
grupo_etario     0.684459
cantidad         0.000000
dtype: float64

departamento        3
municipio           3
armas_medios    51883
fecha_hecho         0
genero            289
grupo_etario     1894
cantidad            0
dtype: int64

Imputación de valores faltantes con la moda#

# Imputar con la moda
df['grupo_etario'] = df['grupo_etario'].fillna(df['grupo_etario'].mode()[0])
df['departamento'] = df['departamento'].fillna(df['departamento'].mode()[0])
df['municipio'] = df['municipio'].fillna(df['municipio'].mode()[0])
df['armas_medios'] = df['armas_medios'].fillna('Unknown')
df['genero'] = df['genero'].fillna(df['genero'].mode()[0])
# Validación de cambios
print(df.isnull().sum())
departamento    0
municipio       0
armas_medios    0
fecha_hecho     0
genero          0
grupo_etario    0
cantidad        0
dtype: int64
df = df.loc[:, ~df.columns.duplicated()].copy()
df['departamento'] = df['departamento'].str.normalize('NFKD').str.encode('ascii', errors='ignore').str.decode('utf-8')
df['departamento'] = df['departamento'].replace({'SAN ANDRES':'ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA',
                             'VALLE':'VALLE DEL CAUCA',
                             'NARINO':'NARIÑO',
                             'GUAJIRA':'LA GUAJIRA'
})
df.departamento.unique()
array(['ANTIOQUIA', 'ATLANTICO', 'BOLIVAR', 'BOYACA', 'CAUCA', 'CESAR',
       'CORDOBA', 'CUNDINAMARCA', 'HUILA', 'MAGDALENA', 'NARIÑO',
       'NORTE DE SANTANDER', 'RISARALDA', 'SANTANDER', 'SUCRE',
       'VALLE DEL CAUCA', 'CALDAS', 'CAQUETA', 'CASANARE', 'GUAINIA',
       'LA GUAJIRA', 'META', 'PUTUMAYO', 'QUINDIO', 'TOLIMA', 'VAUPES',
       'ARAUCA', 'CHOCO',
       'ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA',
       'GUAVIARE', 'VICHADA', 'AMAZONAS'], dtype=object)
df.loc[df['municipio'] == 'BOGOTÁ D.C. (CT)', 'departamento'] = 'SANTAFE DE BOGOTA D.C'
new_categories = {
    'ARMA BLANCA / CORTOPUNZANTE': 'ARMA BLANCA',
    'CORTOPUNZANTES': 'ARMA BLANCA',
    'CORTANTES': 'ARMA BLANCA',
    'CONTUNDENTES': 'ARMA BLANCA',
    'PUNZANTES': 'ARMA BLANCA',
    'Unknown': 'DESCONOCIDA'
}

df['armas_medios'] = df['armas_medios'].replace(new_categories)
print(df['armas_medios'].unique(), 
      df['genero'].unique(), 
      df['grupo_etario'].unique())
['ARMA BLANCA' 'DESCONOCIDA' 'SIN EMPLEO DE ARMAS' 'ARMA DE FUEGO'] ['FEMENINO' 'MASCULINO'] ['ADULTOS' 'ADOLESCENTES' 'MENORES']
df['departamento'] = df['departamento'].astype('category')
df['municipio'] = df['municipio'].astype('category')
df['genero'] = df['genero'].astype('category')
df['grupo_etario'] = df['grupo_etario'].astype('category')
df['armas_medios'] = df['armas_medios'].astype('category')
df['fecha_hecho'] = pd.to_datetime(df['fecha_hecho'])
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 276715 entries, 0 to 276714
Data columns (total 7 columns):
 #   Column        Non-Null Count   Dtype         
---  ------        --------------   -----         
 0   departamento  276715 non-null  category      
 1   municipio     276715 non-null  category      
 2   armas_medios  276715 non-null  category      
 3   fecha_hecho   276715 non-null  datetime64[ns]
 4   genero        276715 non-null  category      
 5   grupo_etario  276715 non-null  category      
 6   cantidad      276715 non-null  int64         
dtypes: category(5), datetime64[ns](1), int64(1)
memory usage: 5.8 MB
df[['cantidad']].query('cantidad > 20').count()
df = df.query('cantidad < 20').reset_index(drop=True).copy()
df.to_parquet(os.path.join("Data",'data_cleaned.parquet'), index=None)
df.to_csv(os.path.join("Data",'data_cleaned.csv'), index=None)